package org.loong.crypto.common.utils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteOrder;

import org.loong.crypto.common.exception.CryptoException;

/**
 * ByteUtils.
 */
public class ByteUtils {

    /**
     * Concatenates the specified byte arrays.
     *
     * @param byteArrays The byte arrays to concatenate, may be {@code null}.
     * @return The resulting byte array.
     */
    public static byte[] concat(byte[]... byteArrays) {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            for (byte[] bytes : byteArrays) {

                if (bytes == null) {
                    continue; // skip
                }
                baos.write(bytes);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            // Should never happen
            throw new IllegalStateException(e.getMessage(), e);
        }
    }

    /**
     * Returns the bit length of the specified byte length.
     *
     * @param byteLength The byte length.
     * @return The bit length.
     */
    public static int bitLength(final int byteLength) {
        return byteLength * 8;
    }

    /**
     * Returns the byte length of the specified byte array.
     *
     * @param byteArray The byte array. May be {@code null}.
     * @return The bite length, zero if the array is {@code null}.
     */
    public static int bitLength(final byte[] byteArray) {
        if (byteArray == null) {
            return 0;
        } else {
            return bitLength(byteArray.length);
        }
    }

    /**
     * Returns the bit length of the specified byte length, preventing integer overflow.
     *
     * @param byteLength The byte length.
     * @return The bit length.
     * @throws IntegerOverflowException On a integer overflow.
     */
    public static int safeBitLength(final int byteLength) throws CryptoException {
        long longResult = (long) byteLength * (long) 8;
        if ((long) ((int) longResult) != longResult) {
            throw new CryptoException();
        } else {
            return (int) longResult;
        }
    }

    /**
     * Returns the byte length of the specified byte array, preventing integer overflow.
     *
     * @param byteArray The byte array. May be {@code null}.
     * @return The bite length, zero if the array is {@code null}.
     * @throws IntegerOverflowException On a integer overflow.
     */
    public static int safeBitLength(final byte[] byteArray) throws CryptoException {
        if (byteArray == null) {
            return 0;
        } else {
            return safeBitLength(byteArray.length);
        }
    }

    /**
     * Returns the byte length of the specified bit length.
     *
     * @param bitLength The bit length.
     * @return The byte byte length.
     */
    public static int byteLength(final int bitLength) {
        return bitLength / 8;
    }

    /**
     * Copy array.
     * 
     * @param bytes the bytes
     * @param srcPos the srcPos
     * @param length the length
     * @return byte array
     */
    public static byte[] copy(byte[] bytes, int srcPos, int length) {
        byte[] buf = new byte[length];
        System.arraycopy(bytes, srcPos, buf, 0, length);
        return buf;
    }

    /**
     * byte array to int.
     *
     * @param bytes the bytes
     * @return int value
     */
    public static int bytesToInt(byte[] bytes) {
        return bytesToInt(bytes, ByteOrder.LITTLE_ENDIAN);
    }

    /**
     * byte array to int.
     *
     * @param bytes the bytes
     * @param byteOrder the byteOrder
     * @return int value
     */
    public static int bytesToInt(byte[] bytes, ByteOrder byteOrder) {
        return bytesToInt(bytes, 0, byteOrder);
    }

    /**
     * byte array to int.
     *
     * @param bytes the bytes
     * @param start the start
     * @param byteOrder the byteOrder
     * @return int value
     */
    public static int bytesToInt(byte[] bytes, int start, ByteOrder byteOrder) {
        if (ByteOrder.LITTLE_ENDIAN == byteOrder) {
            // CHECKSTYLE:OFF
            return bytes[start] & 0xFF | //
                (bytes[1 + start] & 0xFF) << 8 | //
                (bytes[2 + start] & 0xFF) << 16 | //
                (bytes[3 + start] & 0xFF) << 24; //
            // CHECKSTYLE:ON
        } else {
            // CHECKSTYLE:OFF
            return bytes[3 + start] & 0xFF | //
                (bytes[2 + start] & 0xFF) << 8 | //
                (bytes[1 + start] & 0xFF) << 16 | //
                (bytes[start] & 0xFF) << 24; //
            // CHECKSTYLE:ON
        }
    }

    /**
     * int to byte array.
     *
     * @param intValue the intValue
     * @return byte array
     */
    public static byte[] intToBytes(int intValue) {
        return intToBytes(intValue, ByteOrder.LITTLE_ENDIAN);
    }

    /**
     * int to byte array.
     *
     * @param intValue the intValue
     * @param byteOrder the byteOrder
     * @return byte array
     */
    public static byte[] intToBytes(int intValue, ByteOrder byteOrder) {
        if (ByteOrder.LITTLE_ENDIAN == byteOrder) {
            // CHECKSTYLE:OFF
            return new byte[] { (byte) (intValue & 0xFF),
                (byte) ((intValue >> 8) & 0xFF),
                (byte) ((intValue >> 16) & 0xFF),
                (byte) ((intValue >> 24) & 0xFF) };
            // CHECKSTYLE:ON
        } else {
            // CHECKSTYLE:OFF
            return new byte[] { (byte) ((intValue >> 24) & 0xFF),
                (byte) ((intValue >> 16) & 0xFF),
                (byte) ((intValue >> 8) & 0xFF),
                (byte) (intValue & 0xFF) };
            // CHECKSTYLE:ON
        }
    }
}
